#ifndef JLIB_VALUE_H
#define JLIB_VALUE_H

#include <string>
#include <vector>
#include "jlib/container/ordered_map.h"

namespace jlib {

struct value {

  typedef bool bit_t;
  typedef long int_t;
  typedef double flt_t;
  typedef std::string str_t;
  typedef std::vector<value> arr_t;
  typedef linked_hash_map<str_t, value> map_t;

  struct fmt_t {
    size_t indentSpaces;
    size_t colonLSpaces;
    size_t colonRSpaces;
    size_t commaLSpaces;
    size_t commaRSpaces;
  };

  enum type_t {
    Nil = 1,
    Bit,
    Int,
    Flt,
    Str,
    Arr,
    Map
  };

  union data_t {
    bit_t b;
    int_t i;
    flt_t f;
    str_t *s;
    arr_t *a;
    map_t *m;
  };

  struct func_t {
    bit_t (*asBit) (const value *) noexcept;
    int_t (*asInt) (const value *) noexcept;
    flt_t (*asFlt) (const value *) noexcept;
    str_t (*asStr) (const value *) noexcept;
    arr_t (*asArr) (const value *) noexcept;
    map_t (*asMap) (const value *) noexcept;
    void (*toJson) (const value *, str_t &) noexcept;
    void (*toString) (const value *, str_t &, size_t &, const fmt_t &) noexcept;
  };

  value () noexcept : func(vtables) {}
  value (bit_t b) noexcept : func(vtables + type_t::Bit) { this->data.b = b; }
  value (int_t i) noexcept : func(vtables + type_t::Int) { this->data.i = i; }
  value (flt_t f) noexcept : func(vtables + type_t::Flt) { this->data.f = f; }
  value (const char *s) noexcept : func(vtables + type_t::Str) { this->data.s = new(std::nothrow) str_t(s); }
  value (const char *s, size_t n) noexcept : func(vtables + type_t::Str) { this->data.s = new(std::nothrow) str_t(s, n); }
  value (const str_t &s) noexcept : func(vtables + type_t::Str) { this->data.s = new(std::nothrow) str_t(s); }
  value (const arr_t &a) noexcept : func(vtables + type_t::Arr) { this->data.a = new(std::nothrow) arr_t(a); }
  value (const map_t &m) noexcept : func(vtables + type_t::Map) { this->data.m = new(std::nothrow) map_t(m); }
  value (str_t &&s) noexcept : func(vtables + type_t::Str) { this->data.s = new(std::nothrow) str_t(std::forward<str_t>(s)); }
  value (arr_t &&a) noexcept : func(vtables + type_t::Arr) { this->data.a = new(std::nothrow) arr_t(std::forward<arr_t>(a)); }
  value (map_t &&m) noexcept : func(vtables + type_t::Map) { this->data.m = new(std::nothrow) map_t(std::forward<map_t>(m)); }

  bool isErr (void) const noexcept { return this->func == vtables; }
  bool isNil (void) const noexcept { return this->func == vtables + type_t::Nil; }
  bool isBit (void) const noexcept { return this->func == vtables + type_t::Bit; }
  bool isInt (void) const noexcept { return this->func == vtables + type_t::Int; }
  bool isFlt (void) const noexcept { return this->func == vtables + type_t::Flt; }
  bool isStr (void) const noexcept { return this->func == vtables + type_t::Str; }
  bool isArr (void) const noexcept { return this->func == vtables + type_t::Arr; }
  bool isMap (void) const noexcept { return this->func == vtables + type_t::Map; }
  bool isNum (void) const noexcept { return this->isInt() || this->isFlt(); }
  bool isSet (void) const noexcept { return this->isArr() || this->isMap(); }

  bit_t asBit () const noexcept { return this->func->asBit(this); }
  int_t asInt () const noexcept { return this->func->asInt(this); }
  flt_t asFlt () const noexcept { return this->func->asFlt(this); }
  str_t asStr () const noexcept { return this->func->asStr(this); }
  arr_t asArr () const noexcept { return this->func->asArr(this); }
  map_t asMap () const noexcept { return this->func->asMap(this); }

  bit_t &getBit () noexcept { return this->data.b; }
  int_t &getInt () noexcept { return this->data.i; }
  flt_t &getFlt () noexcept { return this->data.f; }
  str_t &getStr () noexcept { return *this->data.s; }
  arr_t &getArr () noexcept { return *this->data.a; }
  map_t &getMap () noexcept { return *this->data.m; }

  const bit_t &getBit () const noexcept { return this->data.b; }
  const int_t &getInt () const noexcept { return this->data.i; }
  const flt_t &getFlt () const noexcept { return this->data.f; }
  const str_t &getStr () const noexcept { return *this->data.s; }
  const arr_t &getArr () const noexcept { return *this->data.a; }
  const map_t &getMap () const noexcept { return *this->data.m; }

  operator bool () noexcept { return !this->isErr(); }
  type_t type () const noexcept { return (type_t)(this->func - vtables); }

  value &operator[] (size_t index) noexcept { return this->data.a->operator[](index); }
  value &operator[] (const str_t &key) noexcept { return this->data.m->operator[](key); }



  value (const value &v) noexcept : func(v.func) {
    if (this->func == vtables + type_t::Str)
      this->data.s = new(std::nothrow) str_t(*v.data.s);
    else if (this->func == vtables + type_t::Arr)
      this->data.a = new(std::nothrow) arr_t(*v.data.a);
    else if (this->func == vtables + type_t::Map)
      this->data.m = new(std::nothrow) map_t(*v.data.m);
    else
      this->data = v.data;
  }
  
  value (value &&v) noexcept : func(v.func), data(v.data) {
    this->data.s = v.data.s;
    v.func = vtables;
    v.data = {0};
  }

  value (type_t t) noexcept : func(vtables + t) {
    if (t == type_t::Str)
      this->data.s = new(std::nothrow) str_t();
    else if (t == type_t::Arr)
      this->data.a = new(std::nothrow) arr_t();
    else if (t == type_t::Map)
      this->data.m = new(std::nothrow) map_t();
  }

  ~value () noexcept {
    if (this->func == vtables + type_t::Str)
      delete this->data.s;
    else if (this->func == vtables + type_t::Arr)
      delete this->data.a;
    else if (this->func == vtables + type_t::Map)
      delete this->data.m;
  }
  
  void clear () noexcept {
    this->~value();
    this->func = vtables;
    this->data = {0};
  }

  bool contains (const str_t &key) const noexcept {
    auto &map = *this->data.m;
    auto iter = map.find(key);
    return iter != map.end();
  }

  value *get (size_t index) noexcept {
    if (!this->isArr())
      return nullptr;
    auto &arr = *this->data.a;
    if (index >= arr.size())
      return nullptr;
    return &arr[index];
  }

  value *get (const str_t &key) noexcept {
    if (!this->isMap())
      return nullptr;
    auto &map = *this->data.m;
    auto iter = map.find(key);
    if (iter == map.end())
      return nullptr;
    return &(*iter).value;
  }

  template<typename... Args>
  void addArr (Args&&... args) noexcept {
    this->data.a->emplace(std::forward<Args>(args)...);
  }

  template<typename... Args>
  void addMap (const str_t &key, Args&&... args) noexcept {
    this->data.m->assign(key, std::forward<Args>(args)...);
  }

  template<typename... Args>
  void addMap (str_t &&key, Args&&... args) noexcept {
    this->data.m->assign(std::forward<str_t>(key), std::forward<Args>(args)...);
  }

  value &operator= (const value &v) noexcept {
    this->~value();
    new (this) value(v);
    return *this;
  }

  value &operator= (value &&v) noexcept {
    this->~value();
    new(this) value(std::forward<value>(v));
    return *this;
  }

  template<typename T>
  value &operator= (T v) noexcept {
    this->~value();
    new(this) value(std::forward<T>(v));
    return *this;
  }

  str_t toJson () const noexcept {
    str_t dst;
    this->func->toJson(this, dst);
    return dst;
  }

  str_t toString () const noexcept {
    static const fmt_t fmt = {4, 0, 1, 0, 0};
    str_t dst;
    size_t indents = 0;
    this->func->toString(this, dst, indents, fmt);
    return dst;
  }

  static value fromJson (const char *json, const char **end = nullptr) noexcept;

public:
  static bit_t nil_as_bit (const value *) noexcept;
  static int_t nil_as_int (const value *) noexcept;
  static flt_t nil_as_flt (const value *) noexcept;
  static str_t nil_as_str (const value *) noexcept;
  static arr_t nil_as_arr (const value *) noexcept;
  static map_t nil_as_map (const value *) noexcept;
  static void nil_to_json (const value *, str_t &) noexcept;
  static void nil_to_string (const value *, str_t &, size_t &, const fmt_t &) noexcept;
  static bit_t bit_as_bit (const value *) noexcept;
  static int_t bit_as_int (const value *) noexcept;
  static flt_t bit_as_flt (const value *) noexcept;
  static str_t bit_as_str (const value *) noexcept;
  static arr_t bit_as_arr (const value *) noexcept;
  static map_t bit_as_map (const value *) noexcept;
  static void bit_to_json (const value *, str_t &) noexcept;
  static void bit_to_string (const value *, str_t &, size_t &, const fmt_t &) noexcept;
  static bit_t int_as_bit (const value *) noexcept;
  static int_t int_as_int (const value *) noexcept;
  static flt_t int_as_flt (const value *) noexcept;
  static str_t int_as_str (const value *) noexcept;
  static arr_t int_as_arr (const value *) noexcept;
  static map_t int_as_map (const value *) noexcept;
  static void int_to_json (const value *, str_t &) noexcept;
  static void int_to_string (const value *, str_t &, size_t &, const fmt_t &) noexcept;
  static bit_t flt_as_bit (const value *) noexcept;
  static int_t flt_as_int (const value *) noexcept;
  static flt_t flt_as_flt (const value *) noexcept;
  static str_t flt_as_str (const value *) noexcept;
  static arr_t flt_as_arr (const value *) noexcept;
  static map_t flt_as_map (const value *) noexcept;
  static void flt_to_json (const value *, str_t &) noexcept;
  static void flt_to_string (const value *, str_t &, size_t &, const fmt_t &) noexcept;
  static bit_t str_as_bit (const value *) noexcept;
  static int_t str_as_int (const value *) noexcept;
  static flt_t str_as_flt (const value *) noexcept;
  static str_t str_as_str (const value *) noexcept;
  static arr_t str_as_arr (const value *) noexcept;
  static map_t str_as_map (const value *) noexcept;
  static void str_to_json (const value *, str_t &) noexcept;
  static void str_to_string (const value *, str_t &, size_t &, const fmt_t &) noexcept;
  static bit_t arr_as_bit (const value *) noexcept;
  static int_t arr_as_int (const value *) noexcept;
  static flt_t arr_as_flt (const value *) noexcept;
  static str_t arr_as_str (const value *) noexcept;
  static arr_t arr_as_arr (const value *) noexcept;
  static map_t arr_as_map (const value *) noexcept;
  static void arr_to_json (const value *, str_t &) noexcept;
  static void arr_to_string (const value *, str_t &, size_t &, const fmt_t &) noexcept;
  static bit_t map_as_bit (const value *) noexcept;
  static int_t map_as_int (const value *) noexcept;
  static flt_t map_as_flt (const value *) noexcept;
  static str_t map_as_str (const value *) noexcept;
  static arr_t map_as_arr (const value *) noexcept;
  static map_t map_as_map (const value *) noexcept;
  static void map_to_json (const value *, str_t &) noexcept;
  static void map_to_string (const value *, str_t &, size_t &, const fmt_t &) noexcept;

  func_t *func;
  data_t data;
  static func_t vtables[];

}; // struct value



} // namespace jlib

#endif // JLIB_VALUE_H